/*
 * Public Credit Information Sharing Platform(PCISP).
 * Copyright (c) 2019-2020 jsxzkingdee and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * Please visit https://www.zhengxinsoft.com if you need additional information or have any questions.
 */

package com.platform.dbs;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.platform.comm.enums.IEnum;
import com.platform.comm.util.DateTimeUtil;
import com.platform.enums.DepartType;
import lombok.Data;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Description: CURD参数封装
 *
 * @author libin Created on 2019/8/27
 **/
@Data
public class CrudModel implements Serializable {

    private static final long serialVersionUID = 738452071692396802L;

    public static final String FIELD_RESOURCE_KEY = "resourceKey";
    public static final String FIELD_RESOURCE_BM = "resourceBm";
    public static final String FIELD_TABLE_NAME = "tableName";
    public static final String FIELD_IS_SUB = "isSub";
    public static final String FIELD_IS_LIST = "isList";
    public static final String FIELD_KEY_VALUES = "keyValues";
    public static final String QUERY_VALUES = "queryValues";
    public static final String FIELD_WHERE_KEY_VALUES = "whereKeyValues";

    /**
     * 连接code
     */
    private String linkCode;
    /**
     * 资源标识符
     */
    private String resourceKey;

    /**
     * 资源编码
     */
    private String resourceBm;

    /**
     * 表名
     */
    private String tableName;

    /**
     * 信息订阅标识
     */
    private String isSub;

    /**
     * 是否清单
     */
    private String isList;

    /**
     * 参数
     */
    private Map<String, Object> keyValues = Maps.newLinkedHashMap();
    /**
     * 查询条件
     */
    private List<List<Object>> queryValues = new ArrayList<>();

    /**
     * 查询条件
     */
    private Map<String, List<Condition<Object>>> whereKeyValues = Maps.newLinkedHashMap();

    /**
     * 归集日期查询sql
     */
    private String sql = "";

    /**
     * 字段条件参数
     *
     * @param <T> 数据类型
     */
    @Data
    public static class Condition<T> implements Serializable {
        private static final long serialVersionUID = 5181971090845926981L;
        private String name;
        private OpType op;
        private T val;

        public Condition(String name, T val) {
            this(name, OpType.EQ, val);
        }

        public Condition(String name, OpType op, T val) {
            this.name = name;
            this.op = op;
            this.val = val;
        }
    }

    /**
     * 分页
     */
    private SearchPage searchPage;

    public CrudModel() {
    }

    public enum OpType {
        /**
         * 条件
         */
        EQ("=", "等于"), NE("<>", "不等于"),
        GT(">", "大于"), LT("<", "小于"),
        GE(">=", "大于等于"), LE("<=", "小于等于"),
        LIKE("LIKE", "包含"), NLIKE("NOT LIKE", "不包含"),
        LLIKE("LIKE", "结束以"), RLIKE("LIKE", "开始以"),
        IN("IN", "在列表"), NOTIN("NOT IN", "不在列表"),
        N("IS NULL", "是空"), NN("IS NOT NULL", "不是空"),
        EMPTY(" = '' ","是空文本"),
        NEMPTY(" != '' ","非空文本");
        @Getter
        private final String op;
        @Getter
        private final String text;

        OpType(String op, String text) {
            this.op = op;
            this.text = text;
        }

        public String getCondition(String field, String namedField) {
            if (Strings.isNullOrEmpty(field)) {
                throw new SqlTypeException("字段名不能为空");
            }
            switch (this) {
                case EQ:
                case GT:
                case GE:
                case LE:
                case LT:
                case LIKE:
                case NLIKE:
                case RLIKE:
                case LLIKE:
                    if (Strings.isNullOrEmpty(namedField)) {
                        throw new SqlTypeException("命名字段名不能为空");
                    }
                    return String.format("%s %s '%s'", field, this.getOp(), namedField);
                case EMPTY:
                case NEMPTY:
                    return String.format("trim(%s) %s", field, this.getOp());
                case NE:
                    if (Strings.isNullOrEmpty(namedField)) {
                        throw new SqlTypeException("命名字段名不能为空");
                    }
                    String con = String.format("%s %s '%s'", field, this.getOp(), namedField);
                    return "(" +
                            con +
                            String.format("or %s is null", field)+
                            ")";
                case IN:
                case NOTIN:
                    if (Strings.isNullOrEmpty(namedField)) {
                        throw new SqlTypeException("命名字段名不能为空");
                    }
                    return String.format("%s %s ('%s')", field, this.getOp(), namedField);
                case N:
                case NN:
                    return String.format("%s %s", field, this.getOp());
                default:
                    throw new SqlTypeException("不支持的操作类型");
            }
        }
        public String getConditionVal(String field, Object value) {
            if (Strings.isNullOrEmpty(field)) {
                throw new SqlTypeException("字段名不能为空");
            }
            switch (this) {
                case EQ:
                case NE:
                case GT:
                case GE:
                case LE:
                case LT:
                case LIKE:
                case NLIKE:
                case RLIKE:
                case LLIKE:
                    if (!Objects.nonNull(value)) {
                        throw new SqlTypeException("命名字段名不能为空");
                    }
                    return String.format("%s %s %s", field, this.getOp(), value);
                case IN:
                case NOTIN:
                    if (Objects.nonNull(value)) {
                        throw new SqlTypeException("命名字段名不能为空");
                    }
                    return String.format("%s %s (%s)", field, this.getOp(), value);
                case N:
                case NN:
                    return String.format("%s %s", field, this.getOp());
                default:
                    throw new SqlTypeException("不支持的操作类型");
            }
        }
    }

    public enum ContactType implements IEnum<String> {
        /**
         * SQL 条件连接符
         */
        AND("AND"), OR("OR"), NOT("NOT");

        private final String text;

        ContactType(String text) {
            this.text = text;
        }

        @Override
        public String getKey() {
            return name();
        }

        @Override
        public String getText() {
            return text;
        }

        @Override
        public boolean is(String key) {
            return name().equalsIgnoreCase(key);
        }
    }

    public void putKeyValue(String key, Object value) {
        if (StringUtils.isNotBlank(key)) {
            this.keyValues.put(key, value);
        }
    }

    public void removeKeyValue(String key) {
        if (StringUtils.isNotBlank(key)) {
            this.keyValues.remove(key);
        }
    }

    public void clearKeyValue(){
        this.keyValues.clear();
    }

    public void putKeyValues(Map<String, Object> keyValues) {
        this.keyValues.putAll(keyValues);
    }

    public void putWhereKeyValue(String key, Object value) {
        if (StringUtils.isNotBlank(key)) {
            if (StringUtils.equals("startDate", key)){
                LocalDateTime localDateTime = DateTimeUtil.parseStdDateTimeStr(value.toString());
                key = "create_date";
                sql = String.format("%s and %s >= '%s' ",sql, key, localDateTime);
            }else if (StringUtils.equals("endDate", key)){
                LocalDate localDate = DateTimeUtil.toLocalDate(value.toString());
                LocalDateTime localDateTime = LocalDateTime.of(localDate, LocalTime.MAX);
                key = "create_date";
                sql = String.format("%s and %s <= '%s' ",sql, key, localDateTime);
            }else if (StringUtils.equals(DepartType.DOMAIN.name(),key)){
                putWhereKeyValue(key, new Condition<>("lydw", OpType.LIKE, value));
            }else if (StringUtils.equals(DepartType.NORMAL.name(),key)){
                putWhereKeyValue(key, new Condition<>("lydw", OpType.EQ, value));
            }else{
                putWhereKeyValue(key, new Condition<>(key, value));
            }
        }
    }

    public void putWhereKeyValue(String key, Condition<Object> value) {
        if (StringUtils.isNotBlank(key)) {
            List<Condition<Object>> list = this.whereKeyValues.get(key);
            if (Objects.isNull(list)) {
                list = new ArrayList<>();
                list.add(value);
                this.whereKeyValues.put(key, list);
            } else {
                list.add(value);
            }
        }
    }

//    public void putWhereKeyValues(Map<String, Object> keyValues) {
//        keyValues.forEach(this::putWhereKeyValue);
//    }

    public Map<String, Object> getWhereMap() {
        Map<String, Object> map = Maps.newLinkedHashMap();
        this.whereKeyValues.forEach((key, list) -> {
            if (!CollectionUtils.isEmpty(list)) {
                map.put(key, list.stream().filter(condition -> key.equals(condition.getName()))
                        .findFirst().map(Condition::getVal).orElse(null));
            }
        });
        return map;
    }

    public Map<String, Object> getParamMap() {
        Map<String, Object> map = Maps.newLinkedHashMap();
        this.whereKeyValues.forEach((key, list) -> {
            if (!CollectionUtils.isEmpty(list)) {
                list.forEach(condition -> map.put(condition.name, condition.getVal()));
            }
        });
        return map;
    }

    public void setWhereMap(Map<String, Object> map) {
        Map<String, List<Condition<Object>>> where = Maps.newLinkedHashMap();
        map.forEach((key, val) -> where.put(key, Collections.singletonList(new Condition<>(key, val))));
        this.whereKeyValues = where;
    }

    @Data
    public static class SearchPage {
        private int page = 0;
        private int size = 10;
        private List<SearchSort> sorts = Lists.newArrayList();

        public static SearchPage of(int page, int size) {
            SearchPage searchPage = new SearchPage();
            searchPage.setPage(page);
            searchPage.setSize(size);
            searchPage.addSort(SearchSort.of("create_date", "desc"));
            return searchPage;
        }

        public static SearchPage of(Pageable pageable) {
            SearchPage searchPage = new SearchPage();
            searchPage.setPage(pageable.getPageNumber());
            searchPage.setSize(pageable.getPageSize());
            searchPage.addSort(SearchSort.of("create_date", "desc"));
            return searchPage;
        }
        public static SearchPage of(Pageable pageable,String property,String direction) {
            SearchPage searchPage = new SearchPage();
            searchPage.setPage(pageable.getPageNumber());
            searchPage.setSize(pageable.getPageSize());
            searchPage.addSort(SearchSort.of(property, direction));
            return searchPage;
        }

        public int getOffset() {
            return page * size;
        }

        void addSort(SearchSort searchSort) {
            this.sorts.add(searchSort);
        }
    }

    @Data
    public static class SearchSort {
        private String property;
        private String direction = "asc";

        public static SearchSort of(String property, String direction) {
            SearchSort searchSort = new SearchSort();
            searchSort.setProperty(property);
            searchSort.setDirection(direction);
            return searchSort;
        }

        public static Sort.Order toOrder(SearchSort searchSort) {
            if (Sort.Direction.DESC.name().equalsIgnoreCase(searchSort.direction)) {
                return Sort.Order.desc(searchSort.property);
            }
            return Sort.Order.asc(searchSort.property);
        }

    }

    public static class SqlTypeException extends RuntimeException {

        private static final long serialVersionUID = -6523174022469182811L;
        @Getter
        private final String message;

        public SqlTypeException(String message) {
            super(message);
            this.message = message;
        }

        public static SqlTypeException of(String message) {
            return new SqlTypeException(message);
        }
    }
}
